/*========== Matrix.java ==========
  Matrix will hold a 2-d array of doubles and have a default size of 4x4.
  Handles basic matrix maintenence and math.
  Creates transformation matricies for tralation, scale and rotate
=========================*/

import java.io.*;
import java.util.*;

public class Matrix {
  
  public static final int DEFAULT_SIZE = 4;
  protected double[][] m;
  
    /*===========Constructors================
     Default constructor creates a 4x4 matrix
     Second constructor creates a 4xN matrix
     */
  public Matrix() {
    m = new double[DEFAULT_SIZE][DEFAULT_SIZE];
  }
  public Matrix(int c) {
    m = new double[DEFAULT_SIZE][c];
  }
  
  /*===========grow================
   Increase the number of columns in a matrix by 10
   You can change the growth factor as you see fit
   */
  public void grow() {
    
    double[][] n = new double[m.length][m[0].length + 10];
    for (int r=0; r<m.length; r++)
      for (int c=0; c<m[r].length; c++)
      n[r][c] = m[r][c];
    
    m = n;
  }
  
  /*======== public void clear() ==========
   Inputs:  
   Returns: 
   Sets every entry in the matrix to 0
   ====================*/
  public void clear() {
    
    for (int i=0; i<m.length; i++) 
      for (int j=0; j<m[i].length; j++) 
      m[i][j] = 0;
    }  
  
  /*===========ident================
   Turns this matrix into the indentity matrix
   You may assume the calling Matrix is square
   */
  public void ident() {
    
    for (int i=0; i<m.length; i++) {
      for (int j=0; j<m[i].length; j++) {
        
  if (i==j)
    m[i][j] = 1;
  else
    m[i][j] = 0;
      }
    }
  }
  
  /*===========scalarMult================
   Inputs:  double x
   
   multiply each element of the calling matrix by x
   */
  public void scalarMult( int s ) {
    
 for (int i=0; i<m.length; i++) 
   for (int j=0; j<m[i].length; j++) 
   m[i][j] = m[i][j] * s;
    }  
  
  /*===========matrixMult================
      Multply matrix n by the calling matrix, modify
      the calling matrix to store the result.
      
      eg.
      In the call a.matrixMult(n), n will remain the same
      and a will now be the product of n * a
      */
  public void matrixMult( Matrix n ) {
    
    double[][] tmp = new double[4][1];
    
    for (int c=0; c<m[0].length; c++) {
      for (int r=0; r<4; r++) 
        tmp[r][0] = m[r][c];
      
      for (int r=0; r<4; r++)
        m[r][c] = n.m[r][0] * tmp[0][0] +
        n.m[r][1] * tmp[1][0] +
        n.m[r][2] * tmp[2][0] +
        n.m[r][3] * tmp[3][0];
    }
  }
  
  /*===========copy================
   Create and return new matrix that is a duplicate 
   of the calling matrix
   */
  public Matrix copy() {
    
    Matrix n = new Matrix( m[0].length );
    for (int r=0; r<m.length; r++)
      for (int c=0; c<m[r].length; c++)
      n.m[r][c] = m[r][c];
    
    return n;
  }
  
  /*===========toString================
   Crate a readable String representation of the 
   calling matrix.
   */
  public String toString() {
    
    String s = "";
    for (int i=0; i<m.length; i++) {
      for (int j=0; j<m[i].length; j++)
        s = s + m[i][j] + " ";
      s = s + "\n";
    }
    return s;
  }
  
  /*===========MakeTranslate================
   Turns the calling matrix into the appropriate
   translation matrix using x, y, and z as the translation
   offsets.
   */
    public void makeTranslate(double x, double y, double z) {
      
      ident();
      m[0][3] = x;
      m[1][3] = y;
      m[2][3] = z;
    }
    
    /*===========MakeScale================
     Turns the calling matrix into the appropriate scale
     matrix using x, y and z as the scale factors.
     */
    public void makeScale(double x, double y, double z) {
      
      ident();
      m[0][0] = x;
      m[1][1] = y;
      m[2][2] = z;
    }
    
    /*=========== MakeRotX ================
     Turns the calling matrix into the appropriate rotation
     matrix using theta as the angle of rotation and X
     as the axis of rotation.
     */
    public void makeRotX(double theta) {
      
      ident();
      m[1][1] = Math.cos( theta );
      m[1][2] = -1 * Math.sin( theta );
      m[2][1] = Math.sin( theta );
      m[2][2] = Math.cos( theta );
    }
    
    /*=========== MakeRotY ================
     Turns the calling matrix into the appropriate rotation
     matrix using theta as the angle of rotation and Y
     as the axis of rotation.
     */
    public void makeRotY(double theta) {
      
      ident();
      m[0][0] = Math.cos( theta );
      m[0][2] = Math.sin( theta );
      m[2][0] = -1 * Math.sin( theta );
      m[2][2] = Math.cos( theta );
    }
    
    /*=========== MakeRotZ ================
     Turns the calling matrix into the appropriate rotation
     matrix using theta as the angle of rotation and axis
      as the axis of rotation.
      */
    public void makeRotZ(double theta) {
      
      ident();
      m[0][0] = Math.cos( theta );
      m[0][1] = -1 * Math.sin( theta );
      m[1][0] = Math.sin( theta );
      m[1][1] = Math.cos( theta );
    }
    
   /* public void makeBezier()
    {
      m[0][0] = -1; m[0][1] = 3; m[0][2] = -3; m[0][3] = 1;
      m[1][0] = -3; m[1][1] = -6; m[1][2] = 3; m[1][3] = 0;
      m[2][0] = -3; m[2][1] = 3; m[2][2] = 0; m[2][3] = 0;
      m[3][0] = 1; m[3][1] = 0; m[3][2] = 0; m[3][3] = 0;
    }
    
    public void makeHermite()
    {
      m[0][0] = 0; m[0][1] = 0; m[0][2] = 0; m[0][3] = 1;
      m[1][0] = 1; m[1][1] = 1; m[1][2] = 1; m[1][3] = 1;
      m[2][0] = 0; m[2][1] = 0; m[2][2] = 1; m[2][3] = 0;
      m[3][0] = 3; m[3][1] = 2; m[3][2] = 1; m[3][3] = 0;
    }
    
    public void makeHermiteInverse()
    {
      m[0][0] = 2; m[0][1] = -2; m[0][2] = 1; m[0][3] = 1;
      m[1][0] = -3; m[1][1] = 3; m[1][2] = -2; m[1][3] = -1;
      m[2][0] = 0; m[2][1] = 0; m[2][2] = 1; m[2][3] = 0;
      m[3][0] = 1; m[3][1] = 0; m[3][2] = 0; m[3][3] = 0;
    }*/
    
     public void makeBezier() {
 
 ident();
 m[0][0] = -1;
 m[0][1] = 3;
 m[0][2] = -3;
 m[0][3] = 1;

 m[1][0] = 3;
 m[1][1] = -6;
 m[1][2] = 3;
 m[1][3] = 0;

 m[2][0] = -3;
 m[2][1] = 3;
 m[2][2] = 0;

 m[3][0] = 1;
 m[3][3] = 0;
    }

    public void generateBezierCoefs(double p1, double p2, 
     double p3, double p4) {

 Matrix mult = new Matrix(4);

 m[0][0] = p1;
 m[1][0] = p2;
 m[2][0] = p3;
 m[3][0] = p4;

 mult.makeBezier();
 matrixMult( mult );
    }

    
}
